Skip to content

Fix GH-20214: PDO::FETCH_DEFAULT unexpected behavior with setFetchMode#21434

Open
iliaal wants to merge 4 commits intophp:PHP-8.4from
iliaal:fix/gh-20214-pdo-fetch-default
Open

Fix GH-20214: PDO::FETCH_DEFAULT unexpected behavior with setFetchMode#21434
iliaal wants to merge 4 commits intophp:PHP-8.4from
iliaal:fix/gh-20214-pdo-fetch-default

Conversation

@iliaal
Copy link
Copy Markdown
Contributor

@iliaal iliaal commented Mar 13, 2026

When setFetchMode(PDO::FETCH_DEFAULT) is called, pdo_stmt_setup_fetch_mode() stores PDO_FETCH_USE_DEFAULT (0) as the statement's default fetch type. Later, do_fetch() tries to resolve PDO_FETCH_USE_DEFAULT by reading stmt->default_fetch_type — which is also 0, creating a circular reference. On 8.4 this silently fell through to FETCH_BOTH; on master it throws a ValueError.

The fix resolves PDO_FETCH_USE_DEFAULT to the connection-level default (stmt->dbh->default_fetch_type) early in pdo_stmt_setup_fetch_mode(), before flags extraction and the mode switch. The connection default is guaranteed to never be PDO_FETCH_USE_DEFAULT (enforced by PDO::setAttribute), so no circular resolution is possible.

Not sure if this should be rebased to the PHP-8.4 branch since the bug affects 8.4 as well (silently returns wrong fetch mode there).

Fixes #20214

Copy link
Copy Markdown
Member

@SakiTakamachi SakiTakamachi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fixes!
I’ve left a few minor comments, but I agree with the overall approach.

Comment on lines +1631 to +1633
if ((mode & ~PDO_FETCH_FLAGS) == PDO_FETCH_USE_DEFAULT) {
mode = stmt->dbh->default_fetch_type;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need to be placed after flags = mode & PDO_FETCH_FLAGS;.
Otherwise, when flags such as PDO_FETCH_GROUP are specified, they will be overwritten with 0.

@@ -0,0 +1,38 @@
--TEST--
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should be included in PDO core rather than pdo_sqlite.
If you write it with reference to tests like those in ext/pdo/tests/pdo_xxx.phpt, it will be executed across all drivers.

Also, pdo_stmt_setup_fetch_mode is used not only by setFetchMode, but also when executing query with a second argument.
Therefore, I recommend adding test cases for those scenarios as well.

It would be even better if you can verify that flags such as PDO_FETCH_GROUP are not being overwritten.

@iliaal
Copy link
Copy Markdown
Contributor Author

iliaal commented Apr 3, 2026

Good catches.

The flags issue was a real bug. I moved the resolution after flags extraction so caller-supplied flags like PDO_FETCH_GROUP are preserved:

flags = mode & PDO_FETCH_FLAGS;

if ((mode & ~PDO_FETCH_FLAGS) == PDO_FETCH_USE_DEFAULT) {
    mode = stmt->dbh->default_fetch_type | flags;
}

Moved the test to ext/pdo/tests/ and added cases for query() with a second argument and for flags preservation through the default resolution path.

@SakiTakamachi
Copy link
Copy Markdown
Member

@iliaal
Thank you!
And it looks like it’s fine to target the 8.4 branch.

iliaal added 3 commits April 3, 2026 09:26
…ment::setFetchMode

When setFetchMode(PDO::FETCH_DEFAULT) is called, mode=0
(PDO_FETCH_USE_DEFAULT) gets stored as the statement's default fetch
type. Later, do_fetch() tries to resolve PDO_FETCH_USE_DEFAULT by
reading stmt->default_fetch_type, which is also 0 — circular
reference that on 8.4 silently fell through to FETCH_BOTH and on
master throws a ValueError.

Resolve PDO_FETCH_USE_DEFAULT to the connection-level default early in
pdo_stmt_setup_fetch_mode(), before flags extraction and the mode
switch, so the rest of the function processes the actual fetch mode.

Closes phpGH-20214
Extract flags before resolving PDO_FETCH_USE_DEFAULT so caller-supplied
flags (e.g. PDO_FETCH_GROUP) are preserved. Move test from pdo_sqlite to
ext/pdo/tests/ for cross-driver coverage. Add test cases for query()
with second argument and flags preservation.
Firebird requires SELECT ... FROM table syntax; bare SELECT 'val' AS col
is not valid. Create a test table to make the test cross-driver compatible.
@iliaal iliaal changed the base branch from master to PHP-8.4 April 3, 2026 13:31
@iliaal iliaal force-pushed the fix/gh-20214-pdo-fetch-default branch from 2d59faa to 40868e4 Compare April 3, 2026 13:31
@iliaal
Copy link
Copy Markdown
Contributor Author

iliaal commented Apr 3, 2026

@iliaal Thank you! And it looks like it’s fine to target the 8.4 branch.

@SakiTakamachi I ported the code to 8.4 all the PDO tests pass, however FreeBSD seems to abort with infra error and in other environments un-related ssl (flaky??) tests seem to be failing which are not at all related to this change.

@iliaal iliaal requested a review from SakiTakamachi April 3, 2026 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PDO::FETCH_DEFAULT unexpected behavior with PDOStatement::setFetchMode

2 participants